package pho.prowon.category;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Observable;
import java.util.StringTokenizer;

import pho.helper.DateConverter;
import pho.prowon.ImageDataContainer;
import pho.prowon.index.IndexFactory;
import pho.prowon.index.IndexImplementation;
import cpa.prowon.Application;


/**
 * @author Peter
 *
 * The root of a Index Category Tree. Through this class images can be added (categorized) and removed.
 *  
 * Note: this class has a natural ordering that is inconsistent with equals. 
 */
public class RootIndexCategory extends IndexCategory
{
	public static final String PROPNAME_REFRESHINDEX = "prowon.RefreshIndex";
	
	private ArrayList allImages = new ArrayList();
	private IndexCategoryDefinition catdef;

	/**
	 * create a new root of a category tree, based on the defintion and containing elements of class iC
	 * the Class iC must implement the ImageDataContainer interface and provide a
	 * default constructor. 
	 * @param def the IndexCategoryDefinition for the tree
	 * @param iC the class that is instantiated for images
	 */ 
	public RootIndexCategory(IndexCategoryDefinition def, Class iC)
	{
		super (null, def.getName());
		catdef = def;
		IndexImplementation index_implementation = null;

		long begin = System.currentTimeMillis();
		long numOfFiles = 0;

		try
		{
			index_implementation = IndexFactory.getIndexImplementation();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		String systemProp =	System.getProperty(PROPNAME_REFRESHINDEX,"true");
		List images = null;
		
		if (Boolean.valueOf(systemProp).booleanValue())
		{
			images = index_implementation.readAndReloadIndex(iC);
		}
		else
		{
			images = index_implementation.readIndex(iC,Boolean.TRUE);
		}

		buildCategoryTree(images);	

		numOfFiles = images.size();

		long end = System.currentTimeMillis();
		if ((numOfFiles) > 0)
		{
			System.out.println ("Time: " + (end-begin) + "ms");
			System.out.println ("Number of Files: " + numOfFiles);
			System.out.println ("Time per File: " + ((end-begin)/numOfFiles) + "ms");
		}		
	}


	/* (non-Javadoc)
	 * @see pho.prowon.index.IndexCategory#addImage(pho.prowon.ImageDataContainer)
	 */
	protected ImageDataContainer addImage(ImageDataContainer image, boolean resort)
	{
		if (!allImages.contains(image))
		{
			allImages.add(image);
			((Observable) image).addObserver(this);
		}
		return image;
	}

	/**
	 * The one and only way to add images to the category tree! The images
	 * will be automatically categorized according to the Category Definition
	 * @param ic the image to be added
	 * @param resort if the images should automatically be resorted
	 */
	public void categorizeImage(ImageDataContainer ic, boolean resort)
	{
		// @TODO if images are imported to the index and are already in there the metadata should not be written either on import ???		
		
		List cats = getCategories(ic,Boolean.TRUE);
		Iterator it = cats.iterator();
		while (it.hasNext())
		{
			VisibleIndexCategory currentCat = (VisibleIndexCategory) it.next();
			currentCat.addImage(ic,resort);
		}		
		addImage (ic, resort);

	}

	/* (non-Javadoc)
	 * @see pho.prowon.index.IndexCategory#getAllImages()
	 */
	public List getAllImages()
	{
		return (List) allImages.clone ();
	}

	/* (non-Javadoc)
	 * @see pho.prowon.index.IndexCategory#getRoot()
	 */
	public RootIndexCategory getRoot()
	{
		return this;
	}

	/**
	 * Indicates if the Image passed would still fit in the Category passed as
	 * first parameter
	 * @param cat the category where the image should reside
	 * @param ic the image
	 * @return true if the category is still appropriate
	 */
	protected boolean hasCategoryChanged(IndexCategory cat, ImageDataContainer ic)
	{
		List cats = getCategories(ic,Boolean.FALSE);
		return  (cats == null || !cats.contains(cat));

	}

	
	/**
	 * This retrieves the category the image would be categorized right now
	 * If the second param is true, the necessary category(ies) will be created
	 * if they do not exist. If the second parameter is false, null can be returned
	 * if no appropriate categories exist
	 * @param ic the Image to be tested 
	 * @param addCategory true if the category(ies) should be added if absent
	 * @return the category - null if no category fits and add was false
	 */

	private List getCategories (ImageDataContainer ic, Boolean addCategory)
	{
		Iterator cni = catdef.getCategoryNames().iterator();
		List currentCats = new ArrayList ();
		String defaultCatValue = "";
		
		defaultCatValue = getDefaultCatTitle();
		currentCats.add (this);
		
		
		while (cni.hasNext())
		{
			String catName = (String) cni.next();
						
			//@PHO FInd a better solution to distinguish pseudo tags from normal tags (sublassing)
			if (catName.equals ("$$PATH$$"))
			{
				String [] catValues = new String [1];
				StringTokenizer st = new StringTokenizer (ic.getPathName(),File.separator);
				while (st.hasMoreTokens())
				{
					catValues[0] = st.nextToken();
					// the last token is already the file name
					// don't create a separate category for that
					if (st.hasMoreTokens ())
					{
						if (addCategory.booleanValue())
						{
							currentCats = addNecessaryCategories (currentCats,catName,catValues);
						}
						else
						{
							currentCats = getNecessaryCategories(currentCats,catName,catValues);
						}					
					}
				}
			}
			else
			{
				String [] catValues = new String [1];
				
				if (catName.equals ("$$YEAR$$"))
				{
					catValues [0] = DateConverter.convertDateFormat(ic.getImageDataTag("DateCreated"), ic.getImageData().getDateFormat(),"yyyy");				
				}
				else if (catName.equals ("$$MONTH$$"))
				{					
					catValues [0] = DateConverter.convertDateFormat(ic.getImageDataTag("DateCreated"), ic.getImageData().getDateFormat(),"MMMM");				
				}
				else if (catName.equals ("$$DAY$$"))
				{
					catValues [0] = DateConverter.convertDateFormat(ic.getImageDataTag("DateCreated"), ic.getImageData().getDateFormat(),"dd");				
				}
				else
				{
					catValues = ic.getImageDataTagArray(catName);
				}
				
				if (catValues[0].equals(""))
				{
					catValues[0] = defaultCatValue;
				}
				
				if (addCategory.booleanValue())
				{
					currentCats = addNecessaryCategories (currentCats,catName,catValues);
				}
				else
				{
					currentCats = getNecessaryCategories(currentCats,catName,catValues);
				}				
			}
			if (currentCats.isEmpty())
			{
				// this means we have no category for that damn pic!
				return null;
			}
		}
		return currentCats;
	}


	/**
	 * @param currentCats
	 * @param catName
	 * @param catValue
	 * @return
	 */
	private List addNecessaryCategories(List currentCats, String catName, String [] catValues)
	{
		ArrayList newCats = new ArrayList ();
		Iterator it = currentCats.iterator();
		while (it.hasNext())
		{
			IndexCategory currentCat = (IndexCategory)it.next();
			for (int i = 0; i < catValues.length; i++)
			{
				newCats.add (currentCat.addCategory(catName,catValues[i]));
			}		
		}
		return newCats;
	}


	/**
	 * @param currentCats
	 * @param catName
	 * @param catValue
	 * @return
	 */
	private List getNecessaryCategories(List currentCats, String catName, String [] catValues)
	{
		ArrayList newCats = new ArrayList ();
		Iterator it = currentCats.iterator();
		while (it.hasNext())
		{
			IndexCategory currentCat = (IndexCategory)it.next();			
			for (int i = 0; i < catValues.length; i++)
			{
				IndexCategory newCat = currentCat.getCategory(catValues[i]);
				if (newCat != null)
				{
					newCats.add(newCat);
				}
			}
		}
		return newCats;
	}

	private String getDefaultCatTitle()
	{
		String defaultCatValue;
		try
		{
			defaultCatValue =
				Application.getInstance().getResourceBundle().getString(
					"category_notcategorized");
		}
		catch (MissingResourceException e)
		{
			System.out.println (e);
			defaultCatValue = "Not Categorized";
		}
		return defaultCatValue;
	}

	/**
	 * Re-initialize the whole Category Tree with a new definition. This will
	 * cause all images in the sub-categories to be recategorized according
	 * to the new definition
	 * @param def
	 */
	public void recategorize (IndexCategoryDefinition def)
	{		
		// remove all sub categories before we set the new
		// category definition because clear requires to access the old categories
		// also get an iterator over a copy of all images first so we can recategorize them 
		// after we cleared the original collection
		List images = getAllImages();
		clear();
		catdef = def;
		setName (def.getName());
		buildCategoryTree(images);	
		refresh ();
	}
	
	/**
	 * The one and only way to remove images from the category tree
	 * @param image the image to be removed
	 */
	public void removeImage (ImageDataContainer image)
	{
		List cats = getCategories(image,Boolean.FALSE);
		if (cats != null)
		{
			Iterator it = cats.iterator();
			while (it.hasNext())
			{
				((VisibleIndexCategory)it.next()).removeImage (image);
			}
		}
		allImages.remove(image);
		((Observable)image).deleteObserver(this);
		refresh ();
	}
	
	/**
	 * Write the current category tree's images to the index using the
	 * default Index Implementation
	 */
	public void writeIndex ()
	{
		try {		
			IndexImplementation ii = IndexFactory.getIndexImplementation ();
			ii.writeIndex(allImages);
		} catch (Exception e) {
				System.out.println (e.getMessage());
		}
			
	}
	
	/* (non-Javadoc)
	 * @see pho.prowon.index.IndexCategory#canBeRenamed()
	 */
	public boolean canBeRenamed ()
	{
		return false;
	}
	
	/* (non-Javadoc) 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object obj)
	{
		if (obj != null && obj instanceof RootIndexCategory)
		{
			return (this.catdef.equals(((RootIndexCategory)obj).catdef));
		}
		return false;				
	}			
	
	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Object o)
	{
		return this.catdef.compareTo (((RootIndexCategory)o).catdef);
	}
	
	/* (non-Javadoc)
	 * @see pho.prowon.index.IndexCategory#rename(java.lang.String)
	 */
	public void rename(String newName)
	{
		// does nothing...
	}

	private void buildCategoryTree (List images)
	{
		Iterator ii = images.iterator();
		while (ii.hasNext())
		{			
			categorizeImage((ImageDataContainer) ii.next(), false);			
		}		
				
		resortAll ();						
	}


	/* (non-Javadoc)
	 * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
	 */
	public void update(Observable o, Object arg)
	{
		categorizeImage((ImageDataContainer) o, true);				
	}	
}
